package org.totoro.boot.extend;

import com.netflix.loadbalancer.Server;
import com.netflix.loadbalancer.ZoneAvoidanceRule;
import com.netflix.niws.loadbalancer.DiscoveryEnabledServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;

/**
 * 自定义 负载均衡
 * <p>
 * <h3>支持的负载均衡类型和作用</h3>
 * <pre>
 *      AbstractLoadBalancerRule
 *          是一个抽象类，里边主要定义了一个ILoadBalancer，就是我们上文所说的负载均衡器，负载均衡器的
 *          功能我们在上文已经说的很详细了，这里就不再赘述，这里定义它的目的主要是辅助负责均衡策略选取
 *          合适的服务端实例。
 *
 *      RandomRule
 *          看名字就知道，这种负载均衡策略就是随机选择一个服务实例，看源码我们知道，在RandomRule的无参
 *          构造方法中初始化了一个Random对象，然后在它重写的choose方法又调用了choose(ILoadBalancer lb
 *          , Object key)这个重载的choose方法，在这个重载的choose方法中，每次利用random对象生成一个不
 *          大于服务实例总数的随机数，并将该数作为下标所以获取一个服务实例。
 *
 *      RoundRobinRule
 *          这种负载均衡策略叫做线性负载均衡策略，也就是我们在上文所说的BaseLoadBalancer负载均衡器中默
 *          认采用的负载均衡策略。这个类的choose(ILoadBalancer lb, Object key)函数整体逻辑是这样的：开
 *          启一个计数器count，在while循环中遍历服务清单，获取清单之前先通过incrementAndGetModulo方法获
 *          取一个下标，这个下标是一个不断自增长的数先加1然后和服务清单总数取模之后获取到的（所以这个下标
 *          从来不会越界），拿着下标再去服务清单列表中取服务，每次循环计数器都会加1，如果连续10次都没有取
 *          到服务，则会报一个警告No available alive servers after 10 tries from load balancer: XXXX。
 *
 *      RetryRule
 *          看名字就知道这种负载均衡策略带有重试功能。首先RetryRule中又定义了一个subRule，它的实现类是
 *          RoundRobinRule，然后在RetryRule的choose(ILoadBalancer lb, Object key)方法中，每次还是采
 *          用RoundRobinRule中的choose规则来选择一个服务实例，如果选到的实例正常就返回，如果选择的服务
 *          实例为null或者已经失效，则在失效时间deadline之前不断的进行重试（重试时获取服务的策略还是RoundR
 *          obinRule中定义的策略），如果超过了deadline还是没取到则会返回一个null。
 *
 *     WeightedResponseTimeRule
 *          是RoundRobinRule的一个子类，在WeightedResponseTimeRule中对RoundRobinRule的功能进行了扩展
 *          ，WeightedResponseTimeRule中会根据每一个实例的运行情况来给计算出该实例的一个权重，然后在挑
 *          选实例的时候则根据权重进行挑选，这样能够实现更优的实例调用。WeightedResponseTimeRule中有
 *          个名叫DynamicServerWeightTask的定时任务，默认情况下每隔30秒会计算一次各个服务实例的权重，权
 *          重的计算规则也很简单，如果一个服务的平均响应时间越短则权重越大，那么该服务实例被选中执行任务
 *          的概率也就越大。
 *
 *      ClientConfigEnabledRoundRobinRule
 *          选择策略的实现很简单，内部定义了RoundRobinRule，choose方法还是采用了RoundRobinRule的choose
 *          方法，所以它的选择策略和RoundRobinRule的选择策略一致，不赘述。
 *
 *      BestAvailableRule
 *          继承自ClientConfigEnabledRoundRobinRule，它在ClientConfigEnabledRoundRobinRule的基础
 *          上主要增加了根据loadBalancerStats中保存的服务实例的状态信息来过滤掉失效的服务实例的功能，然后顺便找出并发请求
 *          最小的服务实例来使用。然而loadBalancerStats有可能为null，如果loadBalancerStats为null，则BestAvailableRule
 *          将采用它的父类即ClientConfigEnabledRoundRobinRule的服务选取策略（线性轮询）。
 *
 *      PredicateBasedRule
 *         是ClientConfigEnabledRoundRobinRule的一个子类，它先通过内部定义的一个过滤器过滤出一部分服务实例清
 *         单，然后再采用线性轮询的方式从过滤出来的结果中选取一个服务实例。
 *
 *      ZoneAvoidanceRule
 *          是PredicateBasedRule的一个实现类，只不过这里多一个过滤条件，ZoneAvoidanceRule中的过滤条
 *          件是以ZoneAvoidancePredicate为主过滤条件和以AvailabilityPredicate为次过滤条件组成的一个叫做CompositePredicate的组
 *          合过滤条件，过滤成功之后，继续采用线性轮询的方式从过滤结果中选择一个出来。
 *
 * </pre>
 *
 *
 * <h3>覆盖原始的负载均衡算法</h3>
 *
 * <pre>
 *     在配置文件中通过 <b>ribbon.NFLoadBalancerRuleClassName=org.totoro.boot.extend.CustomRule</b>
 * </pre>
 */
public class CustomRule extends ZoneAvoidanceRule {


    private final Logger logger = LoggerFactory.getLogger(CustomRule.class);

    @Override
    public Server choose(Object key) {
        /**
         *获取所有的注册好的服务
         */
        List<Server> serverList = this.getPredicate().getEligibleServers(this.getLoadBalancer().getAllServers(), key);

        if (CollectionUtils.isEmpty(serverList)) {
            return null;
        }

        serverList.stream().forEach(server -> {
            /**
             * 获取服务的元数据
             */
            Map<String, String> metadata = ((DiscoveryEnabledServer) server).getInstanceInfo().getMetadata();
            logger.info("server : {} , metadata ：{}", server, metadata);
        });


        return super.choose(key);
    }
}
